/*
 * main.c
 *
 *  Created on: 16/10/2011
 *      Author: utn_so
 */

#include "manejarPDD.h"
#include "config.h"
#include "protocolo_NIPC.h"
#include "sock.h"
#include "semaf.h"
#include <pthread.h>
#include <time.h>
#include <fcntl.h>
#include <netinet/tcp.h>

int cliente;
ptrLstPed pedidosFSInic=NULL;
ptrLstPed pedidosFSFin=NULL;
ptrLstPedDup pedidosDupInic=NULL;
ptrLstPedDup pedidosDupFin=NULL;
ptrNodoCola discosInic=NULL;
ptrNodoCola discosFin=NULL;
ptrNodoCola fsInic=NULL;
ptrNodoCola fsFin=NULL;
ptrMsj msjsInic;
ptrMsj msjsFin;
ptrNodoCola ultimo;
int mutexPedFS;
int mutex2;
int mutexCantEscrit;
int mutexCantEscritComp;
int mutexCantLect;
int cantEscrituras=0;
//int cantEscriturasComp;
int mutexDup;



void* escuchar(void * param);
void* enviarPed(void* param);
void* chequearEstado(void*param);
void* enviarPedDup(void*param);

int main()
{
	int discoConectado=1;
	int fsConectado=1;
	int socketRaid;
	cantDiscosComp=0;
	cantEscrituras=0;
	int cantSectores;
	int discoAceptado=0;
	terminoDisco=0;
	struct sockaddr_in data;
	pthread_t hiloEscucha;
	msjsInic=NULL;
	msjsFin=NULL;

	mutexPedFS=inicia(1,IPC_PRIVATE);
	mutex2=inicia(1,IPC_PRIVATE);
	mutexSock=inicia(1,IPC_PRIVATE);
	mutexCantEscrit=inicia(1,IPC_PRIVATE);
	mutexCantEscritComp=inicia(1,IPC_PRIVATE);
	mutexCantLect=inicia(1,IPC_PRIVATE);
	mutexSectsGlob=inicia(1,IPC_PRIVATE);
	mutexDup=inicia(1,IPC_PRIVATE);
	abrirConfig();

	data.sin_family = AF_INET;
	data.sin_addr.s_addr = inet_addr(ip);
	data.sin_port = htons(atoi(puerto));

	char buffHandShake[1024];
	char buffHandShake2[1024];
	memset(buffHandShake,0,1024);

	data.sin_family=AF_INET;
	data.sin_addr.s_addr=inet_addr(ip);
	data.sin_port=htons(atoi(puerto));

	iniciarSocketA(&socketRaid);

	//int numero = 0;

	//int ret = setsockopt(socketRaid,SOL_SOCKET,SO_SNDBUF,/*(char*)*/&numero,sizeof(numero));
	//ret = setsockopt(socketRaid,SOL_SOCKET,SO_RCVBUF,/*(char*)*/&numero,sizeof(numero));

	/*int flag = 1;

		int ret = setsockopt( socketRaid, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag) );



		if (ret == -1) {

		 printf("Couldn't setsockopt(TCP_NODELAY)\n");

		 exit( EXIT_FAILURE );

		}*/
	int flag=1;
	int ret=setsockopt(socketRaid,SOL_SOCKET,SO_REUSEADDR,(char*)&flag,sizeof(flag));

	if (ret == -1) {
		printf("Fallo setsockopt()\n");
	}
	realizarBindA(socketRaid,data);
	realizarListenA(socketRaid);

	pthread_t hiloPedsFS;
	pthread_create(&hiloPedsFS,NULL,&enviarPed,NULL);

	while(1){
		cliente=0;
		printf("voy a conectar ALGO\n");
		cliente=aceptarClienteA(socketRaid,data);
		recibirDatosA(buffHandShake,cliente);
		int paylen=1;
		int ID=1;
		memcpy(&paylen,buffHandShake+sizeof(uint8_t),sizeof(uint16_t));
		memcpy(&ID,buffHandShake,sizeof(uint8_t));
		if((ID==0)&&(paylen==0)){//aca llega un FS
			if(discoConectado==0){
				enviarDatosA(buffHandShake,1024,cliente);
				printf("Conectado exitosamente con el FileSystem.\n");
				fsConectado=0;
				void*param = (void*)cliente;
				pthread_create(&hiloEscucha,NULL,&escuchar,param);
				agregarFS(&fsInic,&fsFin,cliente,hiloEscucha);
			}
			else{
				char* sindisco="-1 Conexión rechazada por falta de discos.\n";

				memcpy(buffHandShake,sindisco,strlen(sindisco));
				enviarDatosA(buffHandShake,1024,cliente);
				printf("FileSystem rechazado por falta de discos conectados.\n");
				memset(buffHandShake,0,1024);
				close(cliente);
			}
		}
		else{
			NIPC paquete2;

			char*rta2="-1 no puede realizar una operacion sin un handshake previo";
			if(paylen!=0){
				//aca deberia aceptar a un disco, manda su id en el buffer

				char* idDisco;
				idDisco=malloc(paylen*sizeof(char)+1);
				memset(idDisco,0,paylen*sizeof(char)+1);

				char*strC=malloc(sizeof(char)*10);
				char*strH=malloc(sizeof(char)*10);
				char *strS=malloc(sizeof(char)*10);
				int pista,cabeza,sector;
				int i,j;
				for(i=3,j=0;buffHandShake[i]!= '.' ;i++,j++){
					strC[j]=buffHandShake[i];
				}
				strC[j]='\0';
				for(i++,j=0;buffHandShake[i]!= '.';i++,j++){
					strH[j]=buffHandShake[i];
				}
				strH[j]='\0';
				for(i++,j=0;buffHandShake[i]!= '.';i++,j++){
					strS[j]=buffHandShake[i];
				}
				strS[j]='\0';
				for(i++,j=0;buffHandShake[i]!='\0';i++,j++){
					idDisco[j]=buffHandShake[i];
				}
				idDisco[j]='\0';
				pista=atoi(strC);
				cabeza=atoi(strH);
				sector=atoi(strS);
				free(strC);
				free(strH);
				free(strS);
				if(discoConectado==1){
					discoAceptado=1;
					sectores=malloc((pista*sector)*sizeof(int));
					pistaRaid=pista;
					sectorRaid=sector;
					cabezaRaid=cabeza;
					int k;
					for(k=0;k<pista*sector;k++){
						sectores[k]=-1;
					}
				}
				else{
					if(cabezaRaid!=cabeza || pistaRaid!=pista || sectorRaid!=sector){
						char* rechazoPPD="Rechazado por no tener las mismas características de el/los anterior/es.\n";
						memcpy(buffHandShake,rechazoPPD,strlen(rechazoPPD));
						enviarDatosA(buffHandShake,1024,cliente);
						printf("PPD rechazado por no tener las mismas características de el/los anterior/es.\n");
						memset(buffHandShake,0,1024);
						close(cliente);
						discoAceptado=0;
					}
					else{
						discoAceptado=1;
					}
				}
				if(discoAceptado==1){
				memset(buffHandShake2,0,1024);
				paquete2.mensaje=malloc(sizeof(char)*paylen);
				cargarEstructura(&paquete2,0,buffHandShake2);
				serializar_NIPC(paquete2,buffHandShake2);
				enviarNIPC(buffHandShake2,1024,cliente);
				if(flagConsola==1){
					printf("El Proceso RAID comenzó su ejecución.\n");//PARA CONSOLA
					printf("Disco conectado. ID= %s; socket= %d;\n",idDisco,cliente);//PARA CONSOLA
				}
				free(paquete2.mensaje);

				//agregarPDD(&discosInic,&discosFin,idDisco,cliente,hiloEscucha);

				ultimo=agregarPDD(&discosInic,&discosFin,idDisco,cliente,hiloEscucha,pista,cabeza,sector);

				void * param;
				param = (void*)cliente;
				pthread_create(&hiloEscucha,NULL,&escuchar,param);
				pthread_t hiloEnvioDup;
				pthread_create(&hiloEnvioDup,NULL,&enviarPedDup,param);

				if(discoConectado==1){
					discoConectado=0;
					ultimo->completo=1;
					pthread_t hiloCheck;
					pthread_create(&hiloCheck,NULL,&chequearEstado,NULL);
					cantDiscosComp++;
				}
				else{
					free(idDisco);
					if(flagConsola==1){
						char inicio[24];
						time_t tiempoInicio;
						tiempoInicio=time(NULL);
						strncpy(inicio,ctime(&tiempoInicio),24);
						printf("Inicio de sincronización: %s.\n",inicio);//PARA CONSOLA
					}
					cantSectores=pistaRaid*sectorRaid;
					duplicarDisco(discosInic,cantSectores);
					/*if(flagConsola==1){
						time_t tiempoFin;
						tiempoFin=time(NULL);
						printf("Fin de sincronización: %s.\n",ctime(&tiempoFin));//PARA CONSOLA
					}*/
				//}
				}
				imprimirLstPDD(discosInic);//tmp para prueba
				}
			}
		}
	}


	/*printf("ingrese el numero del disco: ");
	scanf("%d",&id);
	while(id!=-1){
		discoConectado=0;
		printf("El Proceso RAID comenzo su ejecución.\n");//PARA CONSOLA
		printf("Se conectó un nuevo PDD, su ID es %d.\n",id);//PARA CONSOLA
		agregarPDD(&discosInic,&discosFin,id);
		//agregarPed(&discosInic,&discosFin,id);
		printf("ingrese el numero del disco (-1 para terminar): ");
		scanf("%d",&id);
	}
	imprimirLstPDD(discosInic);
	//imprimirLstPed(discosInic);

	printf("ingrese sectores q llegan del FS (-1 end): ");
	scanf("%d",&id);
	while(id!=-1){
		distribuirLect(discosInic,id);
		printf("ingrese sectores q llegan del FS (-1 end): ");
		scanf("%d",&id);
	}
	while(discosInic!=NULL){
		quitarPDD(&discosInic,&discosFin);
	}
	imprimirLstPDD(discosInic);*/

	return 0;
}

void* escuchar(void * param)
{
	int sock=(int)param;
	pedido sectorPedido;
	ptrNodoCola discoActual;
	char bufferEscucha[519];
	uint8_t tipo;
	discoActual=buscarSock(discosInic,sock);
	int cont=0;
	while(1){
		memset(bufferEscucha,0,519);
		int ret;
		ret=recibirDatosA(bufferEscucha,sock);
		if(ret>0){
			memcpy(&tipo,bufferEscucha,sizeof(uint8_t));
			char fin[24];
			switch(tipo){
				case 1:
					down(mutexPedFS);
					agregarPed(&pedidosFSInic,&pedidosFSFin,sectorPedido);
					up(mutexPedFS);
				break;
				case 2:
					down(mutexCantLect);
					cont++;
					up(mutexCantLect);
					//printf("llegaron %d\n",cont);
					if(cont==pistaRaid*sectorRaid){
						cont=0;
					}
					uint32_t sectorLeido;
					memcpy(&sectorLeido,bufferEscucha+sizeof(uint8_t)+sizeof(uint16_t)+512,sizeof(uint32_t));
					down(mutexSectsGlob);
					sectores[sectorLeido]=0;
					//printf("Pedido devuelto= %d\n",sectorLeido);
					up(mutexSectsGlob);
					tipo=4;
					memset(bufferEscucha,0,sizeof(uint8_t));
					memcpy(bufferEscucha,&tipo,sizeof(uint8_t));
					//enviarDatosA(bufferEscucha,519,ultimo->socket);
					down(mutexDup);
					agregarPedDup(&(discoActual->pedDupInic),&(discoActual->pedDupFin),bufferEscucha);
					up(mutexDup);
					tipo=2;
					//down(mutexCantEscrit);
					//cantEscrituras++;
					//printf("cant escrit= %d\n",cantEscrituras);
					//if(cantEscrituras==pistaRaid*sectorRaid){
					//	printf("todas las lecturas enviadas\n");
					//	cantEscrituras=0;
					//}
					//up(mutexCantEscrit);
				break;
				case 3:
					down(mutexPedFS);
					agregarPed(&pedidosFSInic,&pedidosFSFin,sectorPedido);
					up(mutexPedFS);
				break;
				case 13:
					terminoDisco=1;
				break;
				case 14:
					if(flagConsola==1){
						time_t tiempoFin;
						tiempoFin=time(NULL);
						strncpy(fin,ctime(&tiempoFin),24);
						printf("Fin de sincronización: %s.\n",fin);//PARA CONSOLA
						ultimo->completo=1;
						cantDiscosComp++;
					}
				break;
				/*case 4:
					if(cantEscrituras<(ultimo->pista*ultimo->sector)-1){
						down(mutexCantEscritComp);
						cantEscriturasComp++;
						printf("cant escrit completas= %d\n",cantEscriturasComp);
						up(mutexCantEscritComp);
					}
					else{
						if(flagConsola==1){
							time_t tiempoFin;
							strncpy(fin,ctime(&tiempoFin),24);
							printf("Fin de sincronización: %s.\n",fin);//PARA CONSOLA
							ultimo->completo=1;
							cantDiscosComp++;
						}
						cantEscrituras=0;
					}
				break;*/
			}
		}
		else{
			close(sock);
			buscarParaEliminar(&discosInic,sock);
			if(tienePedidos(sectores,sock)==0){
				redistribuirPeds(&sectores,sock,discosInic);
			}
			return 0;
		}
	}
	return 0;
}

void* enviarPed(void* param)
{
	pedido sectorEnCola;
	while(1){
		if(pedidosFSInic!=NULL && pedidosFSFin!=NULL){
			down(mutexPedFS);
			sectorEnCola=quitarPed(&pedidosFSInic,&pedidosFSFin);
			up(mutexPedFS);
			ptrNodoCola discoSeleccionado;
			switch(sectorEnCola.tipoAcceso){
			case 1:
				discoSeleccionado=distribuirLect(discosInic,sectorEnCola.sector,sectorEnCola.tipoAcceso);
				down(mutexSectsGlob);
				sectores[sectorEnCola.sector]=sectorEnCola.sock;
				up(mutexSectsGlob);
				if(discoSeleccionado!=NULL && flagConsola==1){
					printf("El sector a leer le fue asignado al disco %s.\n",discoSeleccionado->idPlanif);//PARA CONSOLA
				}
			break;
			case 3:
				if(flagConsola==1){
					printf("Se realiza la escritura en todos los discos.\n");//PARA CONSOLA
				}
				escribirEnDiscos(discosInic,sectorEnCola.sector,sectorEnCola.buffer);
			break;
			}
		}
	}
	return 0;
}

void* chequearEstado(void*param)
{
	while(1){
		if(buscarDiscoCompleto(discosInic)==0){
			printf("Ningún disco en estado consistente. Se procede a cerrar todas las conexiones.\n");
			//close(socketFS);
			ptrNodoCola aux;
			aux=discosInic;
			while(aux!=NULL){
				close(aux->socket);
				buscarParaEliminar(&discosInic,aux->socket);
			}
			discosInic=NULL;
			discosFin=NULL;
			aux=fsInic;
			while(aux!=NULL){
				close(aux->socket);
				quitarPDD(&fsInic,&fsFin);
			}
			fsInic=NULL;
			fsFin=NULL;
			borra_s(mutexPedFS);
			borra_s(mutex2);
			borra_s(mutexSock);
			borra_s(mutexCantEscrit);
			borra_s(mutexCantEscritComp);
			borra_s(mutexCantLect);
			borra_s(mutexSectsGlob);
			borra_s(mutexDup);

			while(pedidosFSInic!=NULL){
				quitarPed(&pedidosFSInic,&pedidosFSFin);
			}
			pedidosFSInic=NULL;
			pedidosFSFin=NULL;

			printf("RAID MUTILADO.\n");

			return 0;
		}
	}

	return 0;
}

void* enviarPedDup(void*param)
{
	int sock=(int)param;
	ptrNodoCola discoActual;
	discoActual=buscarSock(discosInic,sock);
	//char bufferEnvio[519];
	while(1){
		if(discoActual->pedDupInic!=NULL && discoActual->pedDupFin!=NULL){
			//memset(bufferEnvio,0,519);
			down(mutexDup);
			//memcpy(bufferEnvio,quitarPedDup(&pedidosDupInic,&pedidosDupFin),519);
			enviarDatosA(quitarPedDup(&(discoActual->pedDupInic),&(discoActual->pedDupFin)),519,ultimo->socket);
			up(mutexDup);
			cantEscrituras++;
			//printf("cant escrit= %d\n",cantEscrituras);
			if(cantEscrituras==pistaRaid*sectorRaid){
				//printf("todas las lecturas enviadas\n");
				cantEscrituras=0;
			}
		}
	}

	return 0;
}
